策略对象对应的接口。
package com.ciaoshen.thinkinjava.newchapter17;
// 无状态对象模拟函数指针(策略模式)
// 适合用匿名内部类的形式初始化
interface Test<C> {
long test(C container, int times);
}
调用Test
策略对象的Tester
接口。
package com.ciaoshen.thinkinjava.newchapter17;
interface Tester<C> {
long runForName(String name, int testSize, int repeatTimes) throws NoSuchElementException;
void runAll(int testSize, int repeatTimes);
}
用来自动生成测试数据的Generator
接口。也是一个策略对象。
package com.ciaoshen.thinkinjava.newchapter17;
// 无状态对象模拟函数指针(策略模式)
// 适合用匿名内部类的形式初始化
interface Generator<T> {
public T next();
}
用来构造Tester
对象的工厂类的抽象接口。
package com.ciaoshen.thinkinjava.newchapter17;
interface Builder<T> {
T build();
}
Tester是一个负责执行Test策略的工具,它内部维护着一个由一组策略和它们的名字组成的映射Map,
这里GenericTester
已经不仅仅是一个骨架实现了,它已经是一个可以使用的泛型类。实现了Tester
接口定义的所有方法。它需要的是往TESTS
映射表中注册Test
策略对象。
/**
* 测试框架:主要利用了Strategy模式和Builder模式,把测试对象某行为的执行效率的辅助平台和测试逻辑剥离开来。
* 用户只需要把注意力集中在测试逻辑单元的设计上,而把程序性的运行,计时工作交给框架来负责。
* 测试框架的骨架是Tester<C>和Test<C>两个接口提供的服务,即:
* 首先,Test是一个无状态对象,用来模拟函数指针(策略模式)。它的策略由它的 test() 方法定义,
* 1. Test#test(C c, int n):执行 C 类型对象的一系列方法 n 次,并返回总时间开销。
* 然后,Tester是一个负责执行Test策略的工具,它内部维护着一个由一组策略和它们的名字组成的映射Map,
* 2. Tester#runForName(String name, int size, int repeat):根据提供的名称,从映射中找出相应的策略Test,
* 然后执行规模为size的Test#test()测试,每次都得出耗时,然后重复repeat遍,最后计算出单次实验的平均时间。
* 3. Tester#runAll(int size, int repeat) 对映射表中的每一个Test策略都执行规模为size * repeat的测试。
*
* 框架的核心组件:
* 1. Test.java
* 2. Tester.java
* 3. Builder.java
* 4. Generator.java
* 4. GenericTester.java
* 5. StringGenerator.java
* 6. SimpleListTester.java
*
*/
package com.ciaoshen.thinkinjava.newchapter17;
import java.util.*;
// 实现这个泛型类的饿子类会是不可变类。
// 所有字段都是private final。也没有提供getter或setter访问方法。没有暴露成员域引用,尤其是可变成员域Map的引用。
// 但其实是有隐患的:因为构造器没有进行保护性拷贝,一旦用户保留了参与构造对象的引用,我们Tester还是有可能受到攻击。
// 所幸,我们用TesterBuilder构造Tester的时候,无论是Generator还是Test都是匿名内部类的形式。因此没有暴露引用。
class GenericTester<C> implements Tester<C> {
private final Map<String,Test<C>> TESTS;
private final Generator<C> GEN;
public GenericTester(TesterBuilder<C> builder) {
GEN = builder.GEN_TO_BUILD;
TESTS = Collections.unmodifiableMap(builder.TESTS_TO_BUILD);
}
public long runForName(String name, int testSize, int repeat) throws NoSuchElementException {
Test<C> test = TESTS.get(name);
if (test == null) { throw new NoSuchElementException("No test called " + name + "!"); }
long time = 0l;
for (int i = 0; i < repeat; i++) {
time = time + test.test(GEN.next(), testSize);
}
return time/repeat;
}
public void runAll(int testSize, int repeat) {
Formatter f = new Formatter(System.out);
for (Map.Entry<String,Test<C>> entry : TESTS.entrySet()) {
long time = runForName(entry.getKey(), testSize, repeat);
f.format("%15.15s %10d %15d \n", entry.getKey(), testSize, time);
}
}
// 外围类GenericTester的配套Builder,当然是可变的。
// 使用Builder模式,首先是想保护Tester,使其保持不可变性。
// 其次,Tester因为内部可以持有很多个Test对象,构造过程比较复杂。Builder让客户端代码更清晰。
static class TesterBuilder<T> implements Builder<Tester<T>> {
private final Generator<T> GEN_TO_BUILD;
private final Map<String,Test<T>> TESTS_TO_BUILD = new HashMap<>();
public TesterBuilder(Generator<T> gen) { GEN_TO_BUILD = gen; }
public void addTest(String name, Test<T> test) {
TESTS_TO_BUILD.put(name, test);
}
public Tester<T> build() {
return new GenericTester<T>(this);
}
}
}
继承了GenericTester
骨架实现,往注册表里插入了Test
策略对象。测试框架可以运行了。
package com.ciaoshen.thinkinjava.newchapter17;
import java.util.*;
// 因为使用匿名内部类的形式初始化Tester,无法保留进入Tester内部对象的引用。
// 所以尽管Tester和TesterBuilder都没有保护性拷贝,我们的SimplemListTester最终还是不可变的。
final class SimpleListTester extends GenericTester<List<String>> {
public SimpleListTester(TesterBuilder<List<String>> builder) {
super(builder);
}
public static void main(String[] args) {
TesterBuilder<List<String>> testerBuilder = new SimpleListTester.TesterBuilder<List<String>>(new Generator<List<String>>() {
public List<String> next() {
return new ArrayList<String>();
}
});
testerBuilder.addTest("Add", new Test<List<String>>() {
public long test(List<String> list, int times) {
assert list.isEmpty();
Generator<String> gen = StringGenerator.newInstance();
String str = gen.next();
long start = System.nanoTime();
for (int i = 0; i < times; i++) {
list.add(str);
}
long end = System.nanoTime();
assert list.size() == times;
return end - start;
}
});
testerBuilder.addTest("AddAll", new Test<List<String>>() {
public long test(List<String> list, int times) {
assert list.isEmpty();
List<String> tempList = new ArrayList<>();
Generator<String> gen = StringGenerator.newInstance();
for (int i = 0; i < times; i++) {
tempList.add(gen.next());
}
long start = System.nanoTime();
list.addAll(tempList);
long end = System.nanoTime();
assert list.size() == times;
return end - start;
}
});
Tester<List<String>> myTester = testerBuilder.build();
myTester.runAll(10000,10);
}
}
这是一个配套String
生成器。用来生成测试数据。
package com.ciaoshen.thinkinjava.newchapter17;
import java.util.*;
final class StringGenerator implements Generator<String> {
private static final int DEFAULT_LENGTH = 7;
private static Generator<String> GEN = null;
private final char[] UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private final char[] LOWER = "abcdefghijklmnopqrstuvwxyz".toCharArray();
private final Random R = new Random();
private final int STR_LENGTH;
private StringGenerator(int size) { STR_LENGTH = size; }
public static Generator<String> newInstance() { // pre-charge Singleton
if (GEN == null) {
GEN = new StringGenerator(DEFAULT_LENGTH);
}
return GEN;
}
public static Generator<String> newInstance(int size) { // the only public factory return Generator interface
return new StringGenerator(size);
}
public String next() {
StringBuilder sb = new StringBuilder();
sb.append(UPPER[R.nextInt(UPPER.length)]);
for (int i = 0; i < STR_LENGTH-1; i++) {
sb.append(LOWER[R.nextInt(LOWER.length)]);
}
return sb.toString();
}
}